package com.truth.lock.interceptor;

import com.truth.lock.LockInfo;
import com.truth.lock.template.LockTemplate;
import com.truth.lock.annotation.TruthLock;
import com.truth.lock.configure.TruthLockProperties;
import com.truth.lock.enumeration.TruthLockType;
import com.truth.lock.exception.AcquireLockException;
import com.truth.lock.exception.KeyGeneratorException;
import com.truth.lock.key.KeyGenerator;
import com.truth.lock.key.SpElKeyGenerator;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.util.StringUtils;

import java.lang.reflect.Method;
import java.util.Objects;

/**
 * @author truth
 */
@Aspect
public class TruthLockInterceptor implements ApplicationContextAware {

    private final static Logger LOGGER = LoggerFactory.getLogger(TruthLockInterceptor.class);

    private final TruthLockProperties lockProperties;

    private final LockTemplate lockTemplate;

    private ApplicationContext applicationContext;

    public TruthLockInterceptor(TruthLockProperties lockProperties, LockTemplate lockTemplate) {
        this.lockProperties = lockProperties;
        this.lockTemplate = lockTemplate;
    }

    @Pointcut(value = "@annotation(com.truth.lock.annotation.TruthLock)")
    public void pointcut(){}

    @Around(value = "pointcut()")
    public Object lockHandle(ProceedingJoinPoint joinPoint) throws Exception {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method targetMethod = methodSignature.getMethod();

        Object target = joinPoint.getTarget();
        Method realMethod = target.getClass().getDeclaredMethod(methodSignature.getName(),
                targetMethod.getParameterTypes());

        TruthLock truthLock = realMethod.getAnnotation(TruthLock.class);
        TruthLockType lockType = truthLock.executor() != null ? truthLock.executor() : lockProperties.getExecutor();
        if (Objects.isNull(lockType)) {
            throw new AcquireLockException("Please specify lock executor type");
        }

        String key = null;
        if (StringUtils.hasText(truthLock.key())) {
            key = SpElKeyGenerator.generator(truthLock.key(), realMethod, joinPoint.getArgs());
        } else {
            String keyGenerator = StringUtils.hasText(truthLock.keyGenerator()) ? truthLock.keyGenerator() : "defaultKeyGenerator";
            KeyGenerator generator = applicationContext.getBean(keyGenerator, KeyGenerator.class);
            if (Objects.isNull(generator)) {
                throw new KeyGeneratorException("name [" + keyGenerator + "] is not exist");
            }
            key = (String) generator.generate(target, realMethod, joinPoint.getArgs());
        }

        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("target: {}", target);
            LOGGER.debug("realMethod: {}", realMethod);
            LOGGER.debug("targetMethod: {}", targetMethod);
            LOGGER.debug("args: {}", joinPoint.getArgs());
            LOGGER.debug("key: {}", key);
        }

        long expire = truthLock.expire() != 0 ? truthLock.expire() : lockProperties.getExpire();
        long acquireTimeout = truthLock.acquireTimeout() != 0 ? truthLock.acquireTimeout() : lockProperties.getAcquireTimeout();
        long acquireCount = truthLock.acquireCount() != 0 ? truthLock.acquireCount() : lockProperties.getAcquireCount();

        LockInfo lockInfo = lockTemplate.lock(key, acquireCount, expire, acquireTimeout, lockType);
        try {
            if (Objects.nonNull(lockInfo)) {
                Object proceed = joinPoint.proceed();
                lockTemplate.releaseLock(lockInfo);
                return proceed;
            }
        } catch (Throwable e) {
            e.printStackTrace();
        } finally {
            lockTemplate.releaseLock(lockInfo);
        }
        return null;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

}
